package com.ferfalk.simplesearchview;

import ohos.aafwk.ability.Ability;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.StackLayout;
import ohos.agp.components.TabList;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.VectorElement;
import ohos.agp.render.BlendMode;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.window.dialog.BaseDialog;
import ohos.agp.window.dialog.PopupDialog;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.ai.asr.AsrClient;
import ohos.ai.asr.AsrIntent;
import ohos.ai.asr.AsrListener;
import ohos.ai.asr.util.AsrError;
import ohos.ai.asr.util.AsrResultKey;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.bundle.AbilityInfo;
import ohos.bundle.IBundleManager;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.audio.AudioCapturer;
import ohos.media.audio.AudioCapturerInfo;
import ohos.media.audio.AudioStreamInfo;
import ohos.utils.PacMap;

import com.alibaba.fastjson.JSONObject;
import com.ferfalk.listener.MyAsrListener;
import com.ferfalk.listener.PermissionListener;
import com.ferfalk.util.AttrValue;
import com.ferfalk.util.PermissionBridge;
import com.ferfalk.util.TextUtils;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * SimpleSearchView
 *
 * @since 2021-04-16
 */
public class SimpleSearchView extends StackLayout implements PermissionBridge.OnPermissionStateListener, Serializable {
    /**
     * request voice search
     */
    public static final int REQUEST_VOICE_SEARCH = 735;

    /**
     * card corner radius
     */
    public static final int CARD_CORNER_RADIUS = 4;

    /**
     * animation center padding
     */
    public static final int ANIMATION_CENTER_PADDING = 26;

    /**
     * style bar
     */
    public static final String STYLE_BAR = "bar";

    /**
     * style card
     */
    public static final String STYLE_CARD = "card";
    private static final int CARD_PADDING = 6;
    private static final int CARD_ELEVATION = 2;
    private static final float BACK_ICON_ALPHA_DEFAULT = 0.87f;
    private static final float ICONS_ALPHA_DEFAULT = 0.54f;
    private static final int ANIMATION_DURATION = 250;
    private static final int TWO = 2;
    private static final int[][] EMPTY = new int[][]{new int[0]};
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "SimpleSearchView.Tag");
    private static final Map<String, Boolean> COMMAND_MAP = new HashMap<>();

    private Context context;
    private Ability mAbility;
    private Point revealAnimationCenter;
    private CharSequence query;
    private CharSequence oldQuery;
    private boolean isAllowVoiceSearch = false;
    private boolean isSearchOpen = false;
    private boolean isClearingFocus = false;
    private String voiceSearchPrompt = "试着说点什么";

    private String style = STYLE_CARD;

    private ComponentContainer searchContainer;
    private TextField searchEditText;
    private Image backButton;
    private Image clearButton;
    private Image voiceButton;
    private Component bottomLine;

    private TabList tabLayout;
    private int tabLayoutInitialHeight;
    private Image mSearch;
    private Image recordVoice;
    private Text prompt;
    private PopupDialog popupDialog;
    private OnQueryTextListener onQueryChangeListener;
    private SearchViewListener searchViewListener;
    private PermissionListener permissionListener;
    private AbilityInfo.DisplayOrientation displayOrientation = AbilityInfo.DisplayOrientation.PORTRAIT;
    private boolean isSearchClosing = false;
    private boolean isKeepQuery = false;

    private EventHandler mEventHandler;
    private Runnable task;
    private AsrClient asrClient;
    private boolean isRecord = false;
    private ThreadPoolExecutor poolExecutor;
    private AudioCapturer audioCapturer;
    private boolean recognizeOver;
    private boolean voiceAvailable = true;

    /**
     * SimpleSearchView constructor
     *
     * @param context
     */
    public SimpleSearchView(Context context) {
        this(context, null);
    }

    /**
     * SimpleSearchView constructor
     *
     * @param context
     * @param attrSet
     */
    public SimpleSearchView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    /**
     * SimpleSearchView constructor
     *
     * @param context
     * @param attrSet
     * @param styleName
     */
    public SimpleSearchView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        this.context = context;

        inflate();
        initStyle(attrSet, styleName);
        initSearchEditText();
        initListeners();
        showVoice(true);
        setVisibility(Component.INVISIBLE);

        task = () -> {
            // 待执行的操作，由开发者定义
            searchEditText.requestFocus();
            searchEditText.simulateClick();
        };
        mEventHandler = new EventHandler(EventRunner.getMainEventRunner());
        new PermissionBridge().setOnPermissionStateListener(this);
    }

    private void inflate() {
        LayoutScatter.getInstance(context).parse(ResourceTable.Layout_search_view, this, true);
        searchContainer = (ComponentContainer) findComponentById(ResourceTable.Id_searchContainer);
        searchEditText = (TextField) findComponentById(ResourceTable.Id_searchEditText);
        backButton = (Image) findComponentById(ResourceTable.Id_buttonBack);
        clearButton = (Image) findComponentById(ResourceTable.Id_buttonClear);
        voiceButton = (Image) findComponentById(ResourceTable.Id_buttonVoice);
        bottomLine = findComponentById(ResourceTable.Id_bottomLine);
    }

    private void initStyle(AttrSet attrs, String defStyleAttr) {
        if (attrs.getAttr("type").isPresent()) {
            setCardStyle(AttrValue.get(attrs, "type", style));
        }
        if (attrs.getAttr("backIconAlpha").isPresent()) {
            setBackIconAlpha(AttrValue.get(attrs, "backIconAlpha", BACK_ICON_ALPHA_DEFAULT));
        }
        if (attrs.getAttr("iconsAlpha").isPresent()) {
            setIconsAlpha(AttrValue.get(attrs, "iconsAlpha", ICONS_ALPHA_DEFAULT));
        }
        if (attrs.getAttr("backIconTint").isPresent()) {
            setBackIconColor(AttrValue.get(attrs, "backIconTint", "#FF000000"));
        }
        if (attrs.getAttr("iconsTint").isPresent()) {
            setIconsColor(AttrValue.get(attrs, "iconsTint", "#FF000000"));
        }
        if (attrs.getAttr("cursorColor").isPresent()) {
            setCursorColor(AttrValue.get(attrs, "cursorColor", "#FF4081"));
        }
        if (attrs.getAttr("hintColor").isPresent()) {
            setHintTextColor(AttrValue.get(attrs, "hintColor", "#8A000000"));
        }
        if (attrs.getAttr("searchBackground").isPresent()) {
            ShapeElement shapeElement = new ShapeElement();
            String color = AttrValue.get(attrs, "searchBackground", "#FFFFFF");
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor(color)));
            setSearchBackground(shapeElement);
        }
        if (attrs.getAttr("searchBackIcon").isPresent()) {
            setBackIconDrawable(AttrValue.get(attrs, "searchBackIcon",
                new VectorElement(getContext(), ResourceTable.Graphic_ic_arrow_back_black_24dp)));
        }
        if (attrs.getAttr("searchClearIcon").isPresent()) {
            setClearIconDrawable(AttrValue.get(attrs, "searchClearIcon",
                new VectorElement(getContext(), ResourceTable.Graphic_ic_close_black_24dp)));
        }
        if (attrs.getAttr("searchVoiceIcon").isPresent()) {
            setVoiceIconDrawable(AttrValue.get(attrs, "searchVoiceIcon",
                new VectorElement(getContext(), ResourceTable.Graphic_ic_voice_search_black_24dp)));
        }
        if (attrs.getAttr("voiceSearch").isPresent()) {
            enableVoiceSearch(AttrValue.get(attrs, "voiceSearch", isAllowVoiceSearch));
        }
        if (attrs.getAttr("voiceSearchPrompt").isPresent()) {
            setVoiceSearchPrompt(AttrValue.get(attrs, "voiceSearchPrompt", ""));
        }
        if (attrs.getAttr("hint").isPresent()) {
            setHint(AttrValue.get(attrs, "hint", "Search"));
        }
        if (attrs.getAttr("inputType").isPresent()) {
            setInputType(AttrValue.get(attrs, "inputType", TWO));
        }
        if (attrs.getAttr("textColor").isPresent()) {
            setTextColor(AttrValue.get(attrs, "textColor", ResourceTable.Color_default_textColor));
        }
    }

    private void initSearchEditText() {
        searchEditText.addTextObserver((s, i, i1, i2) -> {
            if (!isSearchClosing) {
                SimpleSearchView.this.onTextChanged(s);
            }
        });
        searchEditText.setEditorActionListener(i -> {
            onSubmitQuery();
            return true;
        });
    }

    private void initListeners() {
        backButton.setClickedListener(v -> closeSearch());
        clearButton.setClickedListener(v -> clearSearch());
        voiceButton.setClickedListener(v -> voiceSearch());
    }

    private void initAudioCapturer() {
        poolExecutor =
            new ThreadPoolExecutor(
                3,
                3,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(6),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        AudioStreamInfo audioStreamInfo =
            new AudioStreamInfo.Builder()
                .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT)
                .channelMask(AudioStreamInfo.ChannelMask.CHANNEL_IN_MONO)
                .sampleRate(16000)
                .build();

        AudioCapturerInfo audioCapturerInfo = new AudioCapturerInfo.Builder().audioStreamInfo(audioStreamInfo).build();

        audioCapturer = new AudioCapturer(audioCapturerInfo);
    }

    private boolean recognizeWords(String result) {
        JSONObject jsonObject = JSONObject.parseObject(result);
        JSONObject resultObject = new JSONObject();
        if (jsonObject.getJSONArray("result").get(0) instanceof JSONObject) {
            resultObject = (JSONObject) jsonObject.getJSONArray("result").get(0);
        }
        String resultWord = resultObject.getString("ori_word").replace(" ", "");
        boolean command = COMMAND_MAP.getOrDefault(resultWord, false);
        HiLog.info(TAG, "======" + resultWord + "===" + command);
        mAbility.getMainTaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                if (prompt != null) {
                    prompt.setText(resultWord);
                }
            }
        });

        return command;
    }

    private void initListener() {
        AsrListener asrListener = new MyAsrListener() {
            @Override
            public void onInit(PacMap params) {
                super.onInit(params);
                mAbility.getMainTaskDispatcher().syncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        openAudio();
                    }
                });

                HiLog.info(TAG, "======onInit======");
            }

            @Override
            public void onError(int error) {
                super.onError(error);
                if (error == AsrError.ERROR_NO_ASR) {
                    voiceAvailable = false;
                    showVoice(false);
                } else {
                    voiceAvailable = true;
                }
                HiLog.info(TAG, "======error:" + error);
            }

            @Override
            public void onIntermediateResults(PacMap pacMap) {
                super.onIntermediateResults(pacMap);
                HiLog.info(TAG, "======onIntermediateResults:");
                String result = pacMap.getString(AsrResultKey.RESULTS_INTERMEDIATE);
                boolean recognizeResult = recognizeWords(result);

                if (recognizeResult && !recognizeOver) {
                    recognizeOver = true;
                    asrClient.stopListening();
                }
            }

            @Override
            public void onResults(PacMap results) {
                super.onResults(results);
                HiLog.info(TAG, "======onResults:");
                recognizeOver = false;
                String result = results.getString(AsrResultKey.RESULTS_RECOGNITION);
                mAbility.getMainTaskDispatcher().asyncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        if (popupDialog != null && popupDialog.isShowing()) {
                            popupDialog.destroy();
                        }
                        setQuery(result, true);
                    }
                });
            }

            @Override
            public void onEnd() {
                super.onEnd();
                HiLog.info(TAG, "======onEnd:");
                recognizeOver = false;
                asrClient.stopListening();
                asrClient.startListening(setStartIntent());
            }

            @Override
            public void onBeginningOfSpeech() {
                mAbility.getMainTaskDispatcher().syncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        recordVoice.setPixelMap(ResourceTable.Media_ic_voice_search_api_holo_light);
                    }
                });
            }

            @Override
            public void onEndOfSpeech() {
                mAbility.getMainTaskDispatcher().syncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        recordVoice.setPixelMap(ResourceTable.Media_ic_voice_search_api_holo_dark);
                    }
                });
            }

            @Override
            public void onAudioStart() {
                super.onAudioStart();
            }

            @Override
            public void onAudioEnd() {
                super.onAudioEnd();
            }
        };
        if (asrClient != null) {
            asrClient.init(setInitIntent(), asrListener);
        }
    }

    private void initAsrClient() {
        asrClient = AsrClient.createAsrClient(mAbility).orElse(null);
        TaskDispatcher taskDispatcher = mAbility.getMainTaskDispatcher();
        taskDispatcher.asyncDispatch(
            new Runnable() {
                @Override
                public void run() {
                    initListener();
                }
            });
    }

    public void setAbility(Ability ability) {
        this.mAbility = ability;
    }

    public void setOnQueryTextListener(OnQueryTextListener listener) {
        this.onQueryChangeListener = listener;
    }

    public void setOnSearchViewListener(SearchViewListener listener) {
        this.searchViewListener = listener;
    }

    public void setPermissionListener(PermissionListener listener) {
        this.permissionListener = listener;
    }

    public void setVoiceSearchPrompt(String voiceSearchPrompt) {
        this.voiceSearchPrompt = voiceSearchPrompt;
    }

    public void setKeepQuery(boolean keepQuery) {
        this.isKeepQuery = keepQuery;
    }

    public CharSequence getQuery() {
        return query;
    }

    public boolean isKeepQuery() {
        return isKeepQuery;
    }

    /**
     * Will reset the search background as the default for the selected style
     *
     * @param cardStyle STYLE_CARD or STYLE_BAR
     */
    public void setCardStyle(String cardStyle) {
        this.style = cardStyle;
        LayoutConfig layoutParams = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
        switch (cardStyle) {
            case STYLE_CARD:
                searchContainer.setBackground(getCardStyleBackground());
                bottomLine.setVisibility(Component.HIDE);
                int cardPadding = AttrHelper.vp2px(CARD_PADDING, context);
                layoutParams.setMargins(cardPadding, cardPadding, cardPadding, cardPadding);
                break;
            case STYLE_BAR:
            default:
                ShapeElement element = new ShapeElement();
                try {
                    int color = getResourceManager().getElement(ResourceTable.Color_white).getColor();
                    element.setRgbColor(RgbColor.fromArgbInt(color));
                    searchContainer.setBackground(element);
                    bottomLine.setVisibility(Component.VISIBLE);
                } catch (IOException e) {
                    e.getMessage();
                } catch (NotExistException e) {
                    e.getMessage();
                } catch (WrongTypeException e) {
                    e.getMessage();
                }
                break;
        }
        searchContainer.setLayoutConfig(layoutParams);
    }

    private ShapeElement getCardStyleBackground() {
        ShapeElement element = new ShapeElement();
        try {
            int color = getResourceManager().getElement(ResourceTable.Color_white).getColor();
            element.setRgbColor(RgbColor.fromArgbInt(color));
            element.setGradientOrientation(ShapeElement.Orientation.TOP_TO_BOTTOM);
            element.setCornerRadius(AttrHelper.vp2px(CARD_CORNER_RADIUS, context));
        } catch (IOException e) {
            e.getMessage();
        } catch (NotExistException e) {
            e.getMessage();
        } catch (WrongTypeException e) {
            e.getMessage();
        }
        return element;
    }

    /**
     * Sets the back/up icon alpha; does not set other icons
     *
     * @param alpha
     */
    public void setBackIconAlpha(float alpha) {
        backButton.setAlpha(alpha);
    }

    /**
     * Sets icons alpha, does not set the back/up icon
     *
     * @param alpha
     */
    public void setIconsAlpha(float alpha) {
        clearButton.setAlpha(alpha);
        voiceButton.setAlpha(alpha);
    }

    /**
     * Sets the back/up icon drawable; does not set other icons
     *
     * @param color
     */
    public void setBackIconColor(String color) {
        int color2 = Color.getIntColor(color);
        VectorElement vectorElement = (VectorElement) backButton.getImageElement();
        vectorElement.setStateColorList(EMPTY, new int[]{color2});
        vectorElement.setStateColorMode(BlendMode.SRC_ATOP);
        backButton.setImageElement(vectorElement);
    }

    /**
     * Sets icons colors, does not set back/up icon
     *
     * @param color
     */
    public void setIconsColor(String color) {
        int color2 = Color.getIntColor(color);
        VectorElement clearVectorElement = (VectorElement) clearButton.getImageElement();
        clearVectorElement.setStateColorList(EMPTY, new int[]{color2});
        clearVectorElement.setStateColorMode(BlendMode.SRC_ATOP);
        clearButton.setImageElement(clearVectorElement);
        VectorElement voiceVectorElement = (VectorElement) voiceButton.getImageElement();
        voiceVectorElement.setStateColorList(EMPTY, new int[]{color2});
        voiceVectorElement.setStateColorMode(BlendMode.SRC_ATOP);
        voiceButton.setImageElement(voiceVectorElement);
    }

    /**
     * setCursorColor for searchEditText
     *
     * @param color
     */
    public void setCursorColor(String color) {
        ShapeElement shapeElement = (ShapeElement) searchEditText.getCursorElement();
        int color2 = Color.getIntColor(color);
        shapeElement.setRgbColor(RgbColor.fromArgbInt(color2));
    }

    /**
     * setHintTextColor for searchEditText
     *
     * @param color
     */
    public void setHintTextColor(String color) {
        int color2 = Color.getIntColor(color);
        searchEditText.setHintColor(new Color(color2));
    }

    /**
     * setHint for searchEditText
     *
     * @param hint
     */
    public void setHint(String hint) {
        searchEditText.setHint(hint);
    }

    /**
     * setSearchBackground for searchContainer
     *
     * @param background
     */
    public void setSearchBackground(Element background) {
        if (background != null) {
            searchContainer.setBackground(background);
        }
    }

    /**
     * Sets the back/up icon drawable
     *
     * @param element
     */
    public void setBackIconDrawable(Element element) {
        backButton.setImageElement(element);
    }

    /**
     * Sets (a) custom Drawable for the clear text button
     *
     * @param element
     */
    public void setClearIconDrawable(Element element) {
        clearButton.setImageElement(element);
    }

    /**
     * Sets (a) custom Drawable for the voice search button
     *
     * @param element
     */
    public void setVoiceIconDrawable(Element element) {
        voiceButton.setImageElement(element);
    }

    /**
     * setInputType for searchEditText
     *
     * @param inputType
     */
    public void setInputType(int inputType) {
        searchEditText.setTextInputType(inputType);
    }

    /**
     * setTextColor for searchEditText
     *
     * @param color
     */
    public void setTextColor(int color) {
        searchEditText.setTextColor(new Color(color));
    }

    private void onTextChanged(CharSequence newText) {
        query = newText;
        boolean hasText = !("".equals(newText) || newText == null);

        if (hasText) {
            clearButton.setVisibility(Component.VISIBLE);
            showVoice(false);
        } else {
            clearButton.setVisibility(Component.HIDE);
            if (voiceAvailable) {
                showVoice(true);
            }
        }

        if (onQueryChangeListener != null && !TextUtils.equals(newText, oldQuery)) {
            onQueryChangeListener.onQueryTextChange(newText.toString());
        }
        oldQuery = newText.toString();
    }

    private boolean isInitAsr() {
        return audioCapturer != null && asrClient != null;
    }

    private void initAsr() {
        initAudioCapturer();
        initAsrClient();
    }

    /**
     * If voice is not available on the device, this method call has not effect.
     *
     * @param show true to enable the voice search icon
     */
    public void showVoice(boolean show) {
        if (show && isAllowVoiceSearch) {
            voiceButton.setVisibility(VISIBLE);
        } else {
            voiceButton.setVisibility(HIDE);
        }
    }

    private void onSubmitQuery() {
        CharSequence submittedQuery = searchEditText.getText();
        if (submittedQuery != null && TextUtils.getTrimmedLength(submittedQuery) > 0) {
            if (onQueryChangeListener == null || !onQueryChangeListener.onQueryTextSubmit(submittedQuery.toString())) {
                closeSearch();
                isSearchClosing = true;
                searchEditText.setText(null);
                isSearchClosing = false;
            }
        }
    }

    /**
     * Closes search with animation
     */
    public void closeSearch() {
        closeSearch(true);
    }

    /**
     * Closes search
     *
     * @param animate true if should be animated
     */
    public void closeSearch(boolean animate) {
        if (!isSearchOpen()) {
            return;
        }
        isSearchClosing = true;
        searchEditText.setText(null);
        isSearchClosing = false;
        clearFocus();
        hideSoftInput();
        if (animate) {
            AnimatorProperty.StateChangedListener stateChangedListener = new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                    if (searchViewListener != null) {
                        searchViewListener.onSearchViewClosedAnimation();
                    }
                    setVisibility(Component.HIDE);
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            };
            AnimatorProperty animatorProperty = createAnimatorProperty();
            animatorProperty.alphaFrom(1f);
            animatorProperty.alpha(0f);
            animatorProperty.setDuration(ANIMATION_DURATION);
            animatorProperty.setStateChangedListener(stateChangedListener);
            animatorProperty.start();
        } else {
            setVisibility(INVISIBLE);
        }
        showTabLayout(animate);
        isSearchOpen = false;
        if (searchViewListener != null) {
            searchViewListener.onSearchViewClosed();
        }
    }

    private void clearSearch() {
        searchEditText.setText(null);
        if (onQueryChangeListener != null) {
            onQueryChangeListener.onQueryTextCleared();
        }
    }

    private void voiceSearch() {
        if (isPermissionGranted()) {
            if (isInitAsr()) {
                openAudio();
            } else {
                initAsr();
            }
        } else {
            if (permissionListener != null) {
                permissionListener.onRequestPermission();
            }
        }
    }

    private boolean isPermissionGranted() {
        boolean granted = true;
        for (String permission : PermissionListener.PERMISSIONS) {
            if (mAbility.canRequestPermission(permission)) {
                if (mAbility.verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED) {
                    granted = false;
                }
            }
        }
        return granted;
    }

    private AsrIntent setInitIntent() {
        AsrIntent initIntent = new AsrIntent();
        initIntent.setAudioSourceType(AsrIntent.AsrAudioSrcType.ASR_SRC_TYPE_PCM);
        initIntent.setEngineType(AsrIntent.AsrEngineType.ASR_ENGINE_TYPE_LOCAL);
        return initIntent;
    }

    private void openAudio() {
        if (!isRecord) {
            asrClient.startListening(setStartIntent());
            isRecord = true;
            poolExecutor.submit(new AudioCaptureRunnable());
        }
        showRecordDialog(displayOrientation);
    }

    private void closeAudio() {
        if (isRecord) {
            asrClient.stopListening();
            isRecord = false;
        }
    }

    private void showRecordDialog(AbilityInfo.DisplayOrientation orientation) {
        Component component = LayoutScatter.getInstance(getContext())
            .parse(ResourceTable.Layout_voice_pop_layout, null, false);
        Text title = (Text) component.findComponentById(ResourceTable.Id_title);
        title.setFont(Font.DEFAULT_BOLD);
        recordVoice = (Image) component.findComponentById(ResourceTable.Id_image_voice);
        prompt = (Text) component.findComponentById(ResourceTable.Id_prompt);
        if (null != voiceSearchPrompt && voiceSearchPrompt.length() > 0) {
            prompt.setText(voiceSearchPrompt);
        }
        DisplayAttributes displayAttributes = DisplayManager.getInstance()
            .getDefaultDisplay(mAbility).get().getAttributes();

        popupDialog = new PopupDialog(getContext(), null);
        popupDialog.setCustomComponent(component);

        if (orientation == AbilityInfo.DisplayOrientation.LANDSCAPE) {
            popupDialog.setSize(displayAttributes.width - 530, 280 * 3);
        } else {
            popupDialog.setSize(displayAttributes.width - 30, 300 * 3);
        }
        popupDialog.setAutoClosable(true);
        popupDialog.setDialogListener(new BaseDialog.DialogListener() {
            @Override
            public boolean isTouchOutside() {
                closeAudio();
                return false;
            }
        });
        recordVoice.setClickedListener(component2 -> {
            popupDialog.destroy();
            closeAudio();
        });

        popupDialog.showOnCertainPosition(LayoutAlignment.CENTER, 0, 0);
    }

    /**
     * Obtains PCM audio streams and sends them to the ASR engine during recording.
     *
     * @since 2021-07-06
     */
    private class AudioCaptureRunnable implements Runnable {
        @Override
        public void run() {
            byte[] buffers = new byte[1280];
            audioCapturer.start();
            while (isRecord) {
                int ret = audioCapturer.read(buffers, 0, 1280);
                if (ret <= 0) {
                    HiLog.error(TAG, "======Error read data");
                } else {
                    asrClient.writePcm(buffers, buffers.length);
                }
            }
        }
    }

    private AsrIntent setStartIntent() {
        AsrIntent asrIntent = new AsrIntent();
        asrIntent.setVadEndWaitMs(2000);
        asrIntent.setVadFrontWaitMs(4800);
        asrIntent.setTimeoutThresholdMs(20000);
        return asrIntent;
    }

    private void startShowSearchAnimation() {
        final AnimatorProperty.StateChangedListener stateChangedListener = new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                setVisibility(Component.VISIBLE);
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (searchViewListener != null) {
                    searchViewListener.onSearchViewShownAnimation();
                }
                backButton.setAlpha(backButton.getAlpha());
                clearButton.setAlpha(clearButton.getAlpha());
                voiceButton.setAlpha(voiceButton.getAlpha());
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        };
        if (getAlpha() == 1f) {
            setAlpha(0);
        }
        AnimatorProperty animatorProperty = createAnimatorProperty();
        animatorProperty.alphaFrom(0f);
        animatorProperty.alpha(1f);
        animatorProperty.setDuration(ANIMATION_DURATION);
        animatorProperty.setStateChangedListener(stateChangedListener);
        animatorProperty.start();
    }

    public boolean isSearchOpen() {
        return isSearchOpen;
    }

    /**
     * setClickedListener for image search button
     *
     * @param search
     */
    public void setSearch(Image search) {
        this.mSearch = search;
        mSearch.setClickedListener(component -> {
            showSearch();
        });
    }

    /**
     * set query text
     *
     * @param queryText query text
     * @param submit true to submit the query
     */
    public void setQuery(String queryText, boolean submit) {
        searchEditText.setText(queryText);
        if (queryText != null) {
            this.query = queryText;
        }
        if (submit && !TextUtils.isEmpty(queryText)) {
            onSubmitQuery();
        }
    }

    /**
     * Sets (a) TabLayout that is automatically hidden when the search opens, and shown when the search closes
     *
     * @param tabLayout
     */
    public void setTabLayout(TabList tabLayout) {
        this.tabLayout = tabLayout;
        tabLayoutInitialHeight = this.tabLayout.getHeight();
        this.tabLayout.addTabSelectedListener(new TabList.TabSelectedListener() {
            @Override
            public void onSelected(TabList.Tab tab) {
            }

            @Override
            public void onUnselected(TabList.Tab tab) {
                closeSearch();
            }

            @Override
            public void onReselected(TabList.Tab tab) {
            }
        });
    }

    /**
     * Shows the attached TabLayout
     *
     * @param animate true if should be animated
     */
    public void showTabLayout(boolean animate) {
        if (tabLayout == null) {
            return;
        }

        if (animate) {
            AnimatorValue animator = new AnimatorValue();
            animator.setDuration(ANIMATION_DURATION);
            animator.setValueUpdateListener((animatorValue, v) -> {
                int height = (int) (tabLayoutInitialHeight * v);
                tabLayout.setHeight(height);
            });
            animator.start();
        } else {
            tabLayout.setVisibility(VISIBLE);
        }
    }

    /**
     * Shows search with animation
     */
    public void showSearch() {
        showSearch(true);
    }

    /**
     * Shows search
     *
     * @param animate true to animate
     */
    public void showSearch(boolean animate) {
        if (isSearchOpen()) {
            return;
        }

        if (isKeepQuery) {
            searchEditText.setText((String) query);
        } else {
            searchEditText.setText("");
            onTextChanged("");
        }

        showSoftInput();

        if (animate) {
            startShowSearchAnimation();
        } else {
            setVisibility(Component.VISIBLE);
        }
        hideTabLayout(animate);
        isSearchOpen = true;
        if (searchViewListener != null) {
            searchViewListener.onSearchViewShown();
        }
    }

    /**
     * Hides the attached TabLayout
     *
     * @param animate true if should be animated
     */
    public void hideTabLayout(boolean animate) {
        if (tabLayout == null) {
            return;
        }

        if (animate) {
            AnimatorValue animator = new AnimatorValue();
            animator.setDuration(ANIMATION_DURATION);
            animator.setValueUpdateListener((animatorValue, v) -> {
                int height = (int) (tabLayoutInitialHeight - tabLayoutInitialHeight * v);
                tabLayout.setHeight(height);
            });
            animator.start();
        } else {
            tabLayout.setVisibility(Component.HIDE);
        }
    }

    /**
     * enableVoiceSearch
     *
     * @param voiceSearch
     */
    public void enableVoiceSearch(boolean voiceSearch) {
        isAllowVoiceSearch = voiceSearch;
    }

    /**
     * onBackPressed
     *
     * @return true or false
     */
    public boolean onBackPressed() {
        if (isSearchOpen()) {
            closeSearch();
            return true;
        }
        return false;
    }

    /**
     * onOrientationChanged
     *
     * @param orientation
     */
    public void onOrientationChanged(AbilityInfo.DisplayOrientation orientation) {
        this.displayOrientation = orientation;
        if (popupDialog != null && popupDialog.isShowing()) {
            popupDialog.destroy();
            showRecordDialog(orientation);
        }
    }

    /**
     * 手动弹窗键盘
     *
     * @return boolean
     */
    public boolean showSoftInput() {
        mEventHandler.postTask(task, 1000, EventHandler.Priority.IMMEDIATE);
        return false;
    }

    /**
     * 手动关闭键盘
     *
     * @return boolean
     */
    public boolean hideSoftInput() {
        searchEditText.clearFocus();
        return false;
    }

    /**
     * permission granted
     */
    @Override
    public void onPermissionGranted() {
        if (isInitAsr()) {
            openAudio();
        } else {
            initAsr();
        }
    }

    @Override
    public void onPermissionDenied() {
    }

    /**
     * OnQueryTextListener
     *
     * @since 2021-04-16
     */
    public interface OnQueryTextListener {
        /**
         * onQueryTextSubmit
         *
         * @param query the query text
         * @return true to override the default action
         */
        boolean onQueryTextSubmit(String query);

        /**
         * onQueryTextChange
         *
         * @param newText the query text
         * @return true to override the default action
         */
        boolean onQueryTextChange(String newText);

        /**
         * Called when the query text is cleared by the user.
         *
         * @return true to override the default action
         */
        boolean onQueryTextCleared();
    }

    /**
     * SearchViewListener
     *
     * @since 2021-04-16
     */
    public interface SearchViewListener {
        /**
         * Called instantly when the search opens
         */
        void onSearchViewShown();

        /**
         * Called instantly when the search closes
         */
        void onSearchViewClosed();

        /**
         * Called at the end of the show animation
         */
        void onSearchViewShownAnimation();

        /**
         * Called at the end of the close animation
         */
        void onSearchViewClosedAnimation();
    }
}
